﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Xceed.Chart.Core;
using RouterControl.LocalDb;
using RouterControl.Whs.SingletonService;

namespace RouterControl.WhsAddIn.ConsoleControls
{
    public partial class TrafficGraph : ConsoleControlBase
    {
        #region TrafficGraphInfo class
        private sealed class TrafficGraphInfo
        {
            public Chart TrafficChart
            {
                get;
                private set;
            }

            public Axis AxisX
            {
                get;
                private set;
            }

            public Axis AxisY
            {
                get;
                private set;
            }

            public LineSeries UploadLine
            {
                get;
                private set;
            }

            public LineSeries DownloadLine
            {
                get;
                private set;
            }

            public AxisConstLine MaxUploadLine
            {
                get;
                private set;
            }

            public AxisConstLine MaxDownloadLine
            {
                get;
                private set;
            }

            public TrafficGraphInfo(Xceed.Chart.ChartControl ctl)
            {
                this.TrafficChart = ctl.Charts[0];
                this.AxisX = this.TrafficChart.Axis(StandardAxis.PrimaryX);
                this.AxisY = this.TrafficChart.Axis(StandardAxis.PrimaryY);
                this.UploadLine = (LineSeries)this.TrafficChart.Series[0];
                this.DownloadLine = (LineSeries)this.TrafficChart.Series[1];
                this.MaxUploadLine = this.AxisY.ConstLines[0];
                this.MaxDownloadLine = this.AxisY.ConstLines[1];
            }
        }
        #endregion

        private TrafficGraphInfo _trafficGraphInfo;
        private LocalDbConnector _localDb;
        private LocalDbConnector.DeviceInfo _localDbDevice;
        private DateTime _lastTrafficTimeStamp = DateTime.MinValue;
        private List<LocalDbConnector.TrafficInfo> _trafficData;

        public TrafficGraph(RouterControlConsoleTab2 tab)
			: base(tab)
		{
			if (_log.IsDebugEnabled)
				_log.Debug("Initialization start");

			try
			{
				InitializeComponent();
	
				this.lineBox.Header.Text = Properties.Resources.txtTraffic;

                _trafficGraphInfo = new TrafficGraphInfo(this.trafficGraphCtl);

                _trafficData = new List<LocalDbConnector.TrafficInfo>(
                    Properties.Settings.Default.TrafficGraphLength
                    );

                // clean up graph
                _trafficGraphInfo.UploadLine.Values.Clear();
                _trafficGraphInfo.DownloadLine.Values.Clear();
                _trafficGraphInfo.MaxUploadLine.LineProperties.Width = 0;
                _trafficGraphInfo.MaxDownloadLine.LineProperties.Width = 0;

				// done
				if (_log.IsDebugEnabled)
					_log.Debug("Initialization done");
			}
			catch (Exception e)
			{
				_log.Fatal("Failed to initialize", e);

				throw;
			}
		}

        private void updateStatsTimer_Tick(object sender, EventArgs e)
        {
            try
            {
	            if (_singletonService.IsWindowsServiceRunning() != SingletonService2Factory.ServiceStatus.Running)
	                return;

                if (!this.updateStatsBgWorker.IsBusy)
                    this.updateStatsBgWorker.RunWorkerAsync();
            }
            catch (Exception ex)
            {
                _log.Error("Failed to update stats graph", ex);

                throw;
            }
        }

        private void UpdateTrafficGraph(IEnumerable<LocalDbConnector.TrafficInfo> traffic)
        {
            var trafficGraphLength = Properties.Settings.Default.TrafficGraphLength;

            // update traffic data
            foreach (var t in traffic)
            {
                // update upload
                _trafficData.Add(t);

                if (_trafficData.Count > trafficGraphLength)
                {
                    _trafficData.RemoveRange(
                        0,
                        (
                            _trafficData.Count -
                            trafficGraphLength
                        ));
                }

                // finalize
                if (_lastTrafficTimeStamp < t.TimeStamp)
                    _lastTrafficTimeStamp = t.TimeStamp;
            }

            // update graph
            _trafficGraphInfo.UploadLine.Values.Clear();
            _trafficGraphInfo.DownloadLine.Values.Clear();
            _trafficGraphInfo.AxisX.Labels.Clear();

            var counter = -1;
            var uploadDownloadBytesMax = 0;

            foreach (var t in _trafficData)
            {
                // traffic
                _trafficGraphInfo.UploadLine.Values.Add((double)t.UpBytesPerSecond / 1024.0);
                _trafficGraphInfo.DownloadLine.Values.Add((double)t.DownBytesPerSecond / 1024.0);

                // maximum
                if (t.UpBytesPerSecond > uploadDownloadBytesMax)
                    uploadDownloadBytesMax = t.UpBytesPerSecond;

                if (t.DownBytesPerSecond > uploadDownloadBytesMax)
                    uploadDownloadBytesMax = t.DownBytesPerSecond;

                // label
                counter++;

                if (((counter % 10) == 0) ||
                    (counter == trafficGraphLength - 1))
                {
                    _trafficGraphInfo.AxisX.Labels.Add(
                        t.TimeStamp.ToString(Properties.Resources.txtTrafficGraphDateTimeFormatX)
                        );
                }
                else
                {
                    _trafficGraphInfo.AxisX.Labels.Add(string.Empty);
                }
            }

            // set max/min values
            SetupGraphAxisMaxY(uploadDownloadBytesMax);
        }

        private void TrafficGraph_Load(object sender, EventArgs e)
        {
            if (_log.IsDebugEnabled)
                _log.Debug("Loading start");

            try
            {
	            if (!_singletonService.Channel.IsSelectedDeviceFound())
	            {
	                _tab.ResetState();
	
	                return;
	            }
	
	            // query local db device info
                var dev = _singletonService.Channel.GetSelectedDevice();

                if (!dev.HasValue)
                    throw new Exception("no selected device");

	            _localDb = new LocalDbConnector();
	
	            _localDbDevice = _localDb.EnsureDevice(
	                dev.Value.UniqueDeviceName,
                    dev.Value.Type
	                );

                // start
                this.updateStatsBgWorker.RunWorkerAsync();
	
                // done
                if (_log.IsDebugEnabled)
                    _log.Debug("Loading done");
            }
            catch (Exception ex)
            {
                _log.Fatal("Loading failed", ex);

                throw;
            }
        }

        private void SetupGraphAxisMaxY(int uploadDownloadBytesPSMax)
        {
            var graphMax = 0.0;

            // show maximum values
            var cfg = _singletonService.Channel.GetSelectedDeviceConfigData();

            if (!cfg.HasValue)
            {
                // clear graph info
                _trafficGraphInfo.AxisY.NumericScale.Max = (
                    Properties.States.Default.LastGraphScale <= 1.0 ?
                    1024.0 : // 1Mbit
                    Properties.States.Default.LastGraphScale
                    );

                _trafficGraphInfo.MaxUploadLine.LineProperties.Width = 0;
                _trafficGraphInfo.MaxDownloadLine.LineProperties.Width = 0;

                return;
            }

            if (cfg.Value.UploadMaxBitRate.HasValue)
            {
                var val = (double)cfg.Value.UploadMaxBitRate.Value / 8.0 / 1024.0;

                // max graph scale
                graphMax = Math.Max(graphMax, val);

                // max line
                _trafficGraphInfo.MaxUploadLine.Value = val;
                _trafficGraphInfo.MaxUploadLine.LineProperties.Width = 1;
            }
            else
            {
                _trafficGraphInfo.MaxUploadLine.LineProperties.Width = 0;
            }

            if (cfg.Value.DownloadMaxBitRate.HasValue)
            {
                var val = (double)cfg.Value.DownloadMaxBitRate.Value / 8.0 / 1024.0;

                // max graph scale
                graphMax = Math.Max(graphMax, val);

                // max line
                _trafficGraphInfo.MaxDownloadLine.Value = val;
                _trafficGraphInfo.MaxDownloadLine.LineProperties.Width = 1;
            }
            else
            {
                _trafficGraphInfo.MaxDownloadLine.LineProperties.Width = 0;
            }

            // show all visible traffic
            graphMax = Math.Max(graphMax, (double)uploadDownloadBytesPSMax / 1024.0);

            // determine tolerance for traffic
            // take 10% of the maximum
            var tolerance = (graphMax * 0.1);

            // update if the difference is too large
            var toleranceHalf = (tolerance * 0.5);
            var graphScaleMax = (graphMax + tolerance);

            if ((_trafficGraphInfo.AxisY.NumericScale.Max < graphScaleMax - toleranceHalf) || 
                (_trafficGraphInfo.AxisY.NumericScale.Max > graphScaleMax + toleranceHalf))
            {
                _trafficGraphInfo.AxisY.NumericScale.Max = graphScaleMax;

                // save scaling
                if (Properties.States.Default.LastGraphScale != graphScaleMax)
                {
                    Properties.States.Default.LastGraphScale = graphScaleMax;

                    Properties.States.Default.Save();
                }
            }
        }

        private void updateStatsBgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
	            // gather traffic
	            var traffic = _localDb.GetTrafficInfoSince(
	                _localDbDevice,
	                _lastTrafficTimeStamp,
	                Properties.Settings.Default.TrafficGraphLength
	                );

	            UpdateTrafficGraph(traffic);
            }
            catch (Exception ex)
            {
                _log.Error("Failed to update traffic stats", ex);
            }
        }

        private void updateStatsBgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.trafficGraphCtl.Refresh();
        }
    }
}
